#include "globals.h"
#include "errno.h"

#include "util/debug.h"

#include "proc/kthread.h"
#include "proc/kmutex.h"

/*
 * IMPORTANT: Mutexes can _NEVER_ be locked or unlocked from an
 * interrupt context. Mutexes are _ONLY_ lock or unlocked from a
 * thread context.
 */

void
kmutex_init(kmutex_t *mtx)
{
	sched_queue_init(&(mtx->km_waitq));
	mtx->km_holder = NULL;
}

/*
 * This should block the current thread (by sleeping on the mutex's
 * wait queue) if the mutex is already taken.
 *
 * No thread should ever try to lock a mutex it already has locked.
 */
void
kmutex_lock(kmutex_t *mtx)
{
	/* KASSERT required in grading guideline */
	KASSERT(curthr && (curthr != mtx->km_holder));
	
	/* Not required
	KASSERT(mtx != NULL);
	*/
	
	while(mtx->km_holder != NULL)
	{
		/* this mutex is already hold by someone */
		sched_sleep_on(&(mtx->km_waitq));
		if(mtx->km_holder == curthr)
			break;
	}

	mtx->km_holder = curthr;
	KASSERT(curthr && (mtx->km_holder == curthr));
}

/*
 * This should do the same as kmutex_lock, but use a cancellable sleep
 * instead.
 */
int
kmutex_lock_cancellable(kmutex_t *mtx)
{
	/* KASSERT required in grading guideline */
	KASSERT(curthr && (curthr != mtx->km_holder));
	
	/* Not required
	KASSERT(mtx != NULL);
	*/

	int retval = 0;

	while(mtx->km_holder != NULL)
	{
		/* this mutex is already hold by someone */
		if(-EINTR == (retval = sched_cancellable_sleep_on(&(mtx->km_waitq))))
			break;
		if(mtx->km_holder == curthr)
			break;
	}

	/* this mutex is available */
	if(retval != -EINTR)
		mtx->km_holder = curthr;
	
	return retval;
}

/*
 * If there are any threads waiting to take a lock on the mutex, one
 * should be woken up and given the lock.
 *
 * Note: This should _NOT_ be a blocking operation!
 *
 * Note: Don't forget to add the new owner of the mutex back to the
 * run queue.
 *
 * Note: Make sure that the thread on the head of the mutex's wait
 * queue becomes the new owner of the mutex.
 *
 * @param mtx the mutex to unlock
 */
void
kmutex_unlock(kmutex_t *mtx)
{
	/* KASSERT required in grading guideline */
	KASSERT(curthr && (curthr == mtx->km_holder));

	if(sched_queue_empty(&(mtx->km_waitq)))
	{
		/* no one wait for this mutex */
		mtx->km_holder = NULL;
	}
	else
	{
		/* some one is wait for this mutex */
		kthread_t *new_holder = NULL;
		list_link_t *new_holder_link = NULL;
		new_holder_link = mtx->km_waitq.tq_list.l_prev;
        	new_holder = list_item(new_holder_link, kthread_t, kt_qlink);
		mtx->km_holder = new_holder;
		sched_wakeup_on(&(mtx->km_waitq));
	}
	
	/* KASSERT required in grading guideline */
	KASSERT(curthr != mtx->km_holder);
}
